package biolabs.model.growth

import biolabs.core.Genomic
import biolabs.model.CumulationStateful
import biolabs.model.abscretion.BorderAbscretor
import biolabs.space.ContinuousPosition
import biolabs.space.DiscretePosition
import biolabs.space.GridShapedObject

import scala.collection.JavaConversions._

object GrowableByReceptorDensity {
	trait Genome {
		val growthThreshold: Int
	}
}

trait GrowableByReceptorDensity[
	Self <: GrowableByReceptorDensity[Self, P, C]
			with BorderAbscretor[Self, P, C]
			with CumulationStateful[Self, P],
	P <: DiscretePosition[P, C],
	C <: ContinuousPosition[C, P]
]
extends Growable[Self, P]
with GridShapedObject[P, C]
{

self:
	Self
	with Genomic[GrowableByReceptorDensity.Genome]
=>

	/**
	 * If growth is desired (i.e. the average number of receptors
	 * reached the growth threshold), returns a growth delegate.
	 * Otherwise returns {@code null}.
	 */
	override def growthDelegate(): Option[GrowthDelegate[P]] = {
		// counts of receptors in each membrane element
		val counts = Map() ++ getReceptorCounts()

		// determine the total number of receptors
		val total = counts.foldLeft(0.0)((sum, pc)=>sum+pc._2)

		// Determine the number of elements to grow by.
		// This is calculated as the minimum number a
		// such that total/(counts.size()+a) < growthThreshold.
		val a = (total/genome.growthThreshold).asInstanceOf[Int] - counts.size + 1
		if(a <= 0)
			return None

		// the elements to grow by will be chosen
		// from the closest outer elements
		val outbrd = getShape().getOuterBorder();

		if(a >= outbrd.size()) // desire to grow by the whole outer border
			return Some(new GrowthDelegate(new java.util.LinkedList[P](outbrd)))

		// Not growing by the whole outer border.
		// For each outer element compute the potential to be included in the growth.
		// Let's compute the potential as r/(1+d), where r is the total number of
		// receptors in the neighbor cell elements and d is the distance from the
		// cell's center of gravity (adding 1 to d avoids division by zero).

		class PotentialPosition[
			D <: DiscretePosition[D,Q],
			Q <: ContinuousPosition[Q,D]
		](pot: Double, pos: D)
		extends Comparable[PotentialPosition[D,Q]] {
			val potential = pot
			val position = pos
			override def compareTo(p: PotentialPosition[D,Q]): Int = {
				if(potential < p.potential)
					return 1;
				if(potential > p.potential)
					return -1;
				return 0;
			}
		}
		val potentials = new java.util.PriorityQueue[PotentialPosition[P,C]](outbrd.size());
		val c = getShape().getCenterOfGravity();
		for(b <- outbrd) {
			val d = c.minus(b.continuous()).length()
			var r = 0.0
			for(n <- b.getNeighbors()) {
				counts.get(n) match {
					case Some(cnt) => r += cnt
					case None => // do nothing
				}
			}
			potentials.offer(new PotentialPosition[P,C](r/(d+1), b));
		}

		// insert the a elements with highest potential into the list to grow
		val toAdd = new java.util.LinkedList[P]();
		for(i <- 0 until a)
			toAdd.add(potentials.remove().position);

		return Some(new GrowthDelegate(toAdd))
	}

	/**
	 * Counts the number of receptors in each membrane (border) element.
	 * @return a map of receptor counts indexed by the border elements.
	 */
	private def getReceptorCounts(): Iterable[(P, Double)] = {
		val border = getShape().getBorderElements()
		for(b <- border)
			yield (b, getAbscretionPointsAt(b).foldLeft(0.0)((sum, ap)=>sum+ap._2.getSensorCapacity()))
	}
}